// -*- coding: utf-8 -*-

#ifndef HYBRID_MODEL_H
#define HYBRID_MODEL_H

#include "vnode.h"

#include <vector>
#include <boost/function.hpp>

#include "badiff.h"

#include "elisa/elisa_model.h"
#include "elisa/elisa_constraint.h"

#include "hybrid_var.h"

NAMESPACE_ELISA

typedef boost::shared_ptr<vnodelp::FADBAD_AD>	ADPtr;
typedef boost::shared_ptr<VNIntVec>				IVPtr;

/**
 * @ingroup Hybrid
 * @class ContConstraintRep
 * @brief Representation class of continuous constraints (w.r.t. ODE over time)
 */
class ContConstraintRep : public ConstraintRep
{
public:
	/**
 	 * Default constructor
 	 */
	ContConstraintRep(ADPtr ad, ContVarVec & vars, Time & time)
		: ConstraintRep(),
		  m_ad(ad),
		  m_vars(vars),
		  m_time(time),
		  m_integrated(false),
		  m_leftIV(ad->size),
		  m_rightIV(ad->size)
	{}

	ContConstraintRep(ContVarVec & vars, Time & time, vnodelp::Tfunction f, vnodelp::TFfunction tf)
		: ConstraintRep(),
		  m_ad(new vnodelp::FADBAD_AD(vars.size(), f, tf)),
		  m_vars(vars),
		  m_time(time),
		  m_integrated(false),
		  m_leftIV(vars.size()),
		  m_rightIV(vars.size())
	{}

	/**
 	 * Copy constructor
 	 */
	ContConstraintRep(const ContConstraintRep & rhs)
		: ConstraintRep(),
		  m_ad(rhs.m_ad),
		  m_vars(rhs.m_vars),
		  m_time(rhs.m_time),
		  m_integrated(rhs.m_integrated),
		  m_leftIV(rhs.m_leftIV),
		  m_rightIV(rhs.m_rightIV)
	{
	}

	/**
 	 * Destructor
 	 */
	~ContConstraintRep()
	{
	}

	/**
 	 * Computes in s the variables occurring in *this
 	 * @param s the set of var where the variable are put
 	 */
	virtual void toVariables(VarImplSet & s) const
	{
		for (int i = 0; i < m_vars.size(); ++i)
			s.insert(m_vars[i]->getImplementation());
		s.insert(m_time.getImplementation());
	}

	/**
 	 * Returns the number of occurrences of v in *this
 	 */
	virtual int multiplicity(VariableImpl & v) const
	{
		int c(0);
		for (int i = 0; i < m_vars.size(); ++i)
			if (v == (*m_vars[i]->getImplementation().get()) ||
				v == (*m_time.getImplementation().get()))
				++c;
		return c;
	}

	/**
 	 * @brief Init the internal representation of the variables
 	 * @param st SearchTree
 	 */
	virtual void initVariableImplementation(SearchTree & searchTree, VarImplSet & s)
	{
		for (int i = 0; i < m_vars.size(); ++i)
			if (m_vars[i]->getImplementation() == NULL)
				m_vars[i]->buildImplementation(&searchTree);

		if (m_time.getImplementation() == NULL)
			m_time.buildImplementation(&searchTree);

		toVariables(s);
	}

	ADPtr getAD()
	{
		return m_ad;
	}

	void addContVar(ContVar *var)
	{
		m_vars.push_back(var);
	}

	ContVarVec & getContVars()
	{
		return m_vars;
	}

	Time & getTime()
	{
		return m_time;
	}

	/**
	 * prepare for an integration step
	 */
	void initIntegrate()
	{
		m_integrated = true;
		m_leftAE  = Interval::EmptySet;
		m_rightAE = Interval::EmptySet;
	}

	bool isIntegrated()
	{
		return m_integrated;
	}

	void trailLeft(const VNIntVec & initIV, const VNInterval & pivot, const VNIntVec & ae)
	{
		m_pivot = pivot;
		m_leftIV.assign(initIV.begin(), initIV.end());
		m_leftAE = Interval(ae[0]);
	}

	void trailRight(const VNIntVec & initIV, const VNIntVec & ae)
	{
		Interval d(ae[0]);
		if (m_rightAE.isEmpty()) {
			m_rightAE = d;
			m_rightIV.assign(initIV.begin(), initIV.end());
		} else
			//m_rightAE.hull(d);
			m_rightAE = hull(m_rightAE, d);
	}

	const Interval & getLeftDomain()
	{
		return m_leftAE;
	}

	const Interval & getRightDomain()
	{
		return m_rightAE;
	}

	/**
 	 * Print the constraint
 	 * @param os the stream where the constraint must be printed
 	 */
	void print(std::ostream & os) const
	{
		os << "ContConstraint" << std::endl;
	}

protected:
	ADPtr m_ad;
	ContVarVec & m_vars;
	Time & m_time;

	bool m_integrated;
	VNInterval m_pivot;
	VNIntVec m_leftIV;
	Interval m_leftAE;
	VNIntVec m_rightIV;
	Interval m_rightAE;
};


typedef B<VNInterval> AD_VNInterval;
typedef std::vector<AD_VNInterval> AD_VNIntVec;

typedef boost::function<VNInterval (const VNIntVec, const VNInterval)> GFunc;
typedef boost::function<AD_VNInterval (const AD_VNIntVec, const AD_VNInterval)> DGFunc;
typedef boost::function<void (int n, VNInterval *, 
						const VNInterval *, const VNInterval, void *param)> DFunc;


/**
 * @ingroup Hybrid
 * @class ContConstraint
 * @brief Representation class of continuous constraints (w.r.t. ODE over time)
 */
class ContConstraint : public Constraint
{
public:
	/**
 	 * Default constructor
 	 */
	ContConstraint(ContConstraintRep *rep,
				   GFunc gfunc, DGFunc dgfunc, DFunc dfunc)
		: Constraint(rep),
		  m_gfunc(gfunc), m_dgfunc(dgfunc), m_dfunc(dfunc)
	{
	}

	/**
 	 * Copy constructor
 	 */
	ContConstraint(const ContConstraint & rhs)
		: Constraint(rhs),
		  m_gfunc(rhs.m_gfunc), m_dgfunc(rhs.m_dgfunc), m_dfunc(rhs.m_dfunc)
	{
	}

	/**
 	 * Give the type of the model
 	 * @return a string that contains the name of the type
 	 */
	std::string type() const
	{
		return "ContConstraint";
	}

	ContConstraintRep *rep()
	{
		return dynamic_cast<ContConstraintRep *>(m_rep);
	}

	GFunc& getGFunc()
	{
		return m_gfunc;
	}
	DGFunc& getDGFunc()
	{
		return m_dgfunc;
	}
	DFunc& getDFunc()
	{
		return m_dfunc;
	}

private:
	GFunc  m_gfunc;
	DGFunc m_dgfunc;
	DFunc  m_dfunc;
};


/**
 * @ingroup Hybrid
 * @class HybridModel
 * class for modeling hybrid problems.
 */
class HybridModel : public AndModel
{
public:
	HybridModel(ADPtr ad, IVPtr iv, double maxTime)
		: AndModel(),
		  m_ad(boost::shared_ptr<vnodelp::FADBAD_AD>(ad)), m_iv(iv), m_maxTime(maxTime)
	{
	}

	HybridModel(const HybridModel & rhs)
		: AndModel(rhs), m_ad(rhs.m_ad)
	{
	}

	/**
 	 * Writing on a stream
 	 * @param os the output stream for printing
 	 */
	void print(std::ostream & os) const
	{
		AndModel::print(os);
	}

	/**
 	 * Serialize the constraint in a Stream
 	 * @param o the output stream
 	 */
	virtual bool serialize(std::ostream & os)
	{
		return false;
	}

	/**
 	 * Deserialize the constraint in a Stream
 	 * @param i the input stream
 	 */
	virtual bool deserialize(std::istream & i)
	{
		return false;
	}

	/**
 	 * Compute the variable of the constraint in a set var
 	 * @param v a VarImplSet object where variables are put
 	 */
	void toVariables(VarImplSet & v)
	{
		SetModelRep::iterator it;
		for (it = _models.begin(); it != _models.end(); ++it) {
			(*it)->toVariables(v);
		}
	}

	/**
 	 * build the implementation of the variable in st
 	 */
	void initVariableImplementation(SearchTree & st)
	{
		SetModelRep::iterator it;
		for (it = _models.begin(); it != _models.end(); ++it) {
			(*it)->initVariableImplementation(st);
		}
	}

	/**
 	 * Give the type of the model
 	 * @return a string that contains the name of the type
 	 */
	std::string type() const
	{
		return "HybridModel";
	}

	/**
 	 *
 	 */
	vnodelp::FADBAD_AD *getAD()
	{
		return m_ad.get();
	}

protected:
	ADPtr m_ad;
	IVPtr m_iv;
	double m_maxTime;
};

END_NAMESPACE_ELISA

#endif // HYBRID_MODEL_H
